#pragma once
#include <cassert>
#include "vector2.h"

namespace krryn{
namespace scene{
#pragma pack(push)
#pragma pack(1)

	template
	<
		typename _Vertex
	>
	class vertex_uv_helper{
	public:
		float *m_Buffer;
		float *m_End;
		float *m_Current;

		_Vertex &m_Parent;

		vertex_uv_helper(_Vertex &a_Parent, float *a_Start, 
			float *a_End, float a_Value) : m_Parent(a_Parent){
			m_Buffer = a_Start;
			m_Current = m_Buffer;
			m_End = a_End;

			uv(a_Value);
		}

		vertex_uv_helper &uv(float a_Uv){
			if(m_Current >= m_End){
				throw scene_exception("Buffer out of bounds");
			}

			*m_Current++ = a_Uv;

			return *this;
		}

		operator _Vertex&(){
			return m_Parent;
		}
	};

	template
	<
		typename _Parent,
		unsigned int _Uv0 = 2,
		unsigned int _Uv1 = 0,
		unsigned int _Uv2 = 0,
		unsigned int _Uv3 = 0,
		unsigned int _Uv4 = 0,
		unsigned int _Uv5 = 0,
		unsigned int _Uv6 = 0,
		unsigned int _Uv7 = 0,
		unsigned int _Uv8 = 0,
		unsigned int _Uv9 = 0
	>
	class vertex_uv{
	public:
		size_t size(){
			return m_Size;
		}

		const char *get_data() const { return (const char*)&m_Buffer; }

		static size_t coord_count(const size_t a_Index){
			switch(a_Index){
				case 0: return _Uv0;
				case 1: return _Uv1;
				case 2: return _Uv2;
				case 3: return _Uv3;
				case 4: return _Uv4;
				case 5: return _Uv5;
				case 6: return _Uv6;
				case 7: return _Uv7;
				case 8: return _Uv8;
				case 9: return _Uv9;
				default: return 0;
			}
		}

		float *get_uv(const size_t a_Index = 0){
			return m_Buffer + offset(a_Index);
		}

		const float *get_uv(const size_t a_Index = 0) const{
			return m_Buffer + offset(a_Index);
		}

		float &get_uv_at(const size_t a_Index, const size_t a_Coord){
			assert(a_Coord < coord_count(a_Index));
			return *(m_Buffer + offset(a_Index) + a_Coord);
		}

		const float& get_uv_at(const size_t a_Index, const size_t a_Coord) const{
			assert(a_Coord < coord_count(a_Index));
			return *(m_Buffer + offset(a_Index) + a_Coord);
		}

		// this is the reason we can't chain the uv() call...
		vertex_uv_helper<_Parent> uv(float a_Value){
			return vertex_uv_helper<_Parent>(
				static_cast<_Parent&>(*this), 
				m_Buffer, m_Buffer + m_Total, 
				a_Value
			);
		}

		vertex_uv_helper<_Parent> uv(const math::uv &a_Uv){
			// chained like this to do error checking 
			// twice without code duplication :-)
			return uv(a_Uv.m_U).uv(a_Uv.m_V);
		}

		static const size_t m_Size = 
			  (_Uv0 > 0 ? 1 : 0)
			+ (_Uv1 > 0 ? 1 : 0)
			+ (_Uv2 > 0 ? 1 : 0)
			+ (_Uv3 > 0 ? 1 : 0)
			+ (_Uv4 > 0 ? 1 : 0)
			+ (_Uv5 > 0 ? 1 : 0)
			+ (_Uv6 > 0 ? 1 : 0)
			+ (_Uv7 > 0 ? 1 : 0)
			+ (_Uv8 > 0 ? 1 : 0)
			+ (_Uv9 > 0 ? 1 : 0);

		static const size_t m_Total = 
			    _Uv0 + _Uv1 + _Uv2 + _Uv3 + _Uv4 
			  + _Uv5 + _Uv6 + _Uv7 + _Uv8 + _Uv9;

		static const size_t m_Stride = sizeof(float) * m_Total;

		static const bool m_Available = m_Size > 0;
	private:
		static size_t offset(const size_t a_Index){
			size_t l_Offset = 0;
			// fall thoughs are on purpose to calculate the offset
			switch(a_Index){
				case 9: l_Offset += _Uv8;
				case 8: l_Offset += _Uv7;
				case 7: l_Offset += _Uv6;
				case 6: l_Offset += _Uv5;
				case 5: l_Offset += _Uv4;
				case 4: l_Offset += _Uv3;
				case 3: l_Offset += _Uv2;
				case 2: l_Offset += _Uv1;
				case 1: l_Offset += _Uv0;
			}
			return l_Offset;
		}

		float m_Buffer[m_Total];
	};

	class vertex_no_uv{
	public:
		size_t size(){
			return 0;
		}

		size_t coord_count(const size_t a_Index) const{
			return 0;
		}

		static const size_t m_Size = 0;
		static const size_t m_Total = 0;
		static const size_t m_Stride = 0;
		static const bool m_Available = false;
	};
#pragma pack(pop)
}
}